Skip to content

Add Droid SDK provider#2689

Open
0xSero wants to merge 17 commits into
pingdotgg:mainfrom
0xSero:ai/droid-sdk-provider
Open

Add Droid SDK provider#2689
0xSero wants to merge 17 commits into
pingdotgg:mainfrom
0xSero:ai/droid-sdk-provider

Conversation

@0xSero
Copy link
Copy Markdown

@0xSero 0xSero commented May 14, 2026

Intention

Add Droid as a first-class T3 Code provider using Factory's TypeScript SDK: https://github.com/Factory-AI/droid-sdk-typescript

This is still WIP while we validate more real Droid permission, file, MCP, auth, and long-running streaming flows.

What this adds

  • Adds droid to the shared provider, model, settings, runtime source, and driver contracts.
  • Registers a managed Droid provider driver plus inventory probe.
  • Wraps createSession / resumeSession from @factory/droid-sdk in a T3 provider adapter.
  • Supports Droid session start, resume, stop, interrupt, and in-session model / reasoning updates.
  • Streams assistant text, reasoning text, tool progress/results, token usage, MCP/auth status, title updates, turn completion, and runtime errors into canonical T3 runtime events.
  • Routes Droid permission callbacks through T3 approval requests.
  • Routes Droid ask-user callbacks through the existing structured user-input flow.
  • Sends supported image attachments (gif, jpeg, png, webp) as base64 SDK image sources resolved through the existing attachment store.
  • Discovers SDK-reported Droid models from initResult.availableModels, including user/custom models, while preserving custom model ids so duplicate underlying models remain selectable.
  • Adds Droid UI presence in Provider Settings and the model picker, including the provided icon.

Implementation shape

The Droid adapter has been split by responsibility instead of keeping one large file:

  • DroidAdapter.ts: session lifecycle and adapter orchestration.
  • provider/droid/DroidRuntimeEvents.ts: SDK message to T3 runtime event projection.
  • provider/droid/DroidSdkMappings.ts: SDK model, access, reasoning, usage, approval, and user-input mappings.
  • provider/droid/DroidAttachmentResolver.ts: attachment MIME validation and image loading.
  • provider/droid/DroidAdapterTypes.ts: shared Droid adapter types.

Security / safety notes

  • Droid remains disabled by default.
  • No SDK secrets, pairing tokens, or provider auth payloads are intentionally logged.
  • Permission requests are mediated through T3 approval events and default to cancellation on missing/stale sessions.
  • Attachment paths are resolved through the existing attachment store helper; unsupported MIME types fail before SDK submission.
  • The Droid binary path is configurable but defaults to droid; prompt content is sent through the SDK rather than shell interpolation.
  • Model discovery uses a short-lived SDK session in a temp cwd and closes it immediately after reading init metadata.

Validation

  • Real Droid CLI query: droid exec --model glm-5.1 --cwd /tmp ... returned droid-pong.
  • Live SDK model discovery returned 67 models, including 38 user/custom models.
  • Local T3 model picker showed custom Droid models such as HomeLab - GLM-5.1, HomeLab - Trinity-Large-Thinking, Direct - GPT-5.5-Fast-xHigh, and Direct - GPT-5.5-Low.
  • Review comments addressed: missing-session reads now fail; Droid rollback now honors numTurns; streaming, thinking, access-mode mapping, and custom model discovery were updated.
  • bun fmt passed.
  • bun lint passed with existing unrelated web warnings.
  • bun typecheck passed.
  • Focused Droid tests passed: cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts.
  • Full bun run test previously passed; after rebasing, one full parallel run hit three unrelated web timeout failures, and rerunning those exact files in isolation passed.

Note

Add Droid SDK provider with session management, streaming turns, and provider-aware runtime modes

  • Adds a new droid provider driver (DroidDriver.ts) that manages provider snapshots with 5-minute refresh, version/installation checks, and model discovery via a short-lived SDK session.
  • Implements a full adapter (DroidAdapter.ts) supporting session start/resume, streaming turns with text and image attachments, permission/user-input request handling, turn interruption, and token usage tracking.
  • Adds a medium-access runtime mode mapped to on-request approval policy and workspace-write sandbox; Droid exposes this as an additional composer access option with provider-specific labels.
  • Registers the Droid provider in the server's built-in drivers, contracts (settings schema, model defaults, display name), and the web UI's provider picker and settings panel.
  • All text generation methods (commit message, PR content, branch name, thread title) on the Droid driver deterministically fail with a TextGenerationError.
  • resolveProviderDriverKindForInstanceSelection now returns undefined when the matched instance is disabled or unavailable.
  • Risk: Droid provider is marked WIP in the settings UI; text generation is unsupported and will always error.

Macroscope summarized 23191e9.


Note

Medium Risk
Introduces a new provider driver/adapter that spawns external sessions, streams runtime events, and adds a new RuntimeMode value; mistakes could impact turn dispatch, approvals, or provider selection across the app.

Overview
Adds Droid as a first-class provider end-to-end: new server DroidDriver + DroidAdapter wrap @factory/droid-sdk to start/resume/stop/interrupt sessions, validate image attachments, route permission + ask-user callbacks through T3 approvals/user-input, and project SDK streaming messages into canonical runtime events (tool lifecycle, token usage, metadata, errors).

Adds a Droid provider snapshot probe that checks the droid CLI (--version) and discovers available models via a short-lived SDK session, plus registers the driver in the built-in registry and defaults/contracts/settings (DroidSettings, provider display names/default models, runtime raw-source enums).

Extends RuntimeMode with medium-access (Droid-specific) and updates web provider selection + composer controls to show provider-specific access options, normalize incompatible modes when switching providers, and avoid resolving disabled/unavailable instance selections (with updated tests/icons/settings metadata).

Reviewed by Cursor Bugbot for commit 23191e9. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5b5aa82f-51dd-44ad-9270-b68b142aa649

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 14, 2026
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 14, 2026
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Latest Droid follow-up fixes pushed in c72f19b:

  • Fixed streaming by reconciling SDK assistant_text_delta block IDs with final create_message content, so final messages fill missing text without duplicating streamed text.
  • Added final create_message fallback handling for assistant text and thinking blocks when deltas are absent.
  • Passed custom model IDs and reasoning effort into Droid spec mode via enterSpecMode({ specModeModelId, specModeReasoningEffort }) and updateSettings(...), which fixes thinking/spec mode for custom models.
  • Matched Droid access levels to SDK autonomy levels: Off, Low, Medium, High, while keeping Medium visible only for Droid UI.
  • Verified the live app model picker shows user/custom Droid models including Direct - GPT-5.5-Fast-xHigh, and the Droid access menu shows Off/Low/Medium/High.
  • Direct SDK smoke query with custom xHigh model returned droid-smoke-ok.

Validation: bun fmt, focused Droid adapter/provider tests, bun typecheck, bun lint, and full bun run test all pass.

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Addressed the unresolved Droid cleanup review thread in b1dabf8. stopSession now treats droid.close() as best-effort cleanup, matching the scoped finalizer behavior, so stopAll() can continue shutting down the remaining sessions even if one SDK close throws.

Added a regression test covering two resumed Droid sessions where one close() fails and verified both sessions are still removed.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts
  • bun run test full suite

Note: the first full-suite run hit a timeout in @t3tools/oxlint-plugin-t3code; that package passed in isolation, then the full suite passed on rerun.

@0xSero 0xSero changed the title WIP: Add Droid SDK provider Add Droid SDK provider May 15, 2026
@0xSero 0xSero marked this pull request as ready for review May 15, 2026 23:25
Comment thread apps/server/src/provider/Layers/DroidProvider.ts Outdated
Comment thread apps/web/src/components/chat/runtimeModePresentation.ts
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 15, 2026

Approvability

Verdict: Needs human review

This PR introduces a new provider integration (Droid SDK) with significant new capability including new runtime mode, provider-specific UI behavior, and ~3k lines of new code. Two unresolved review comments identify bugs in the implementation.

You can customize Macroscope's approvability policy. Learn more.

@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Split the Droid adapter by responsibility in af7ffb0:

  • DroidAdapter.ts is now 433 lines and only owns session lifecycle / adapter orchestration.
  • provider/droid/DroidRuntimeEvents.ts handles Droid SDK message to runtime event projection.
  • provider/droid/DroidSdkMappings.ts handles SDK enum, model, access, user-input, and usage mappings.
  • provider/droid/DroidAttachmentResolver.ts handles attachment image validation/loading.
  • provider/droid/DroidAdapterTypes.ts holds shared adapter types.

The Droid implementation is now 989 lines total across focused modules instead of one nearly 900-line adapter file.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • focused Droid tests: cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts
  • full bun run test was rerun after rebase and hit three unrelated web timeout failures under parallel load; rerunning those exact three files in isolation passed: cd apps/web && bun run test src/environments/runtime/service.addSavedEnvironment.test.ts src/environments/runtime/service.threadSubscriptions.test.ts src/components/chat/MessagesTimeline.test.tsx

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the two remaining Cursor Bugbot review threads in 8939e57:

  • Removed the redundant Droid pending-provider status ternary. The probe stays warning, while buildServerProvider correctly exposes top-level status: "disabled" whenever Droid settings are disabled. Added a regression test for the disabled Droid provider snapshot.
  • Normalized Droid-only medium-access when the selected provider is no longer Droid, falling back to auto-accept-edits and persisting that normalized draft value so the runtime-mode select cannot show a value outside its option list. Added runtime-mode presentation tests.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • focused tests for DroidProvider and runtimeModePresentation
  • full bun run test passed: 125 files passed, 1 skipped; 1032 tests passed, 4 skipped.

@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the remaining token-usage review thread in 742ab64:

  • Droid now stores activeTokenUsage as T3 Code’s canonical ThreadTokenUsageSnapshot instead of the raw SDK TokenUsageUpdate.
  • thread.token-usage.updated and turn.completed now use the same canonical usage shape.
  • Extended DroidAdapter.test.ts to assert both event payloads match the expected canonical token accounting.

Validation after this fix:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts

Earlier in this pass, full bun run test also passed before the final token-usage-only patch: 125 files passed, 1 skipped; 1032 tests passed, 4 skipped.

Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Fixed the latest Cursor Bugbot thread:

  • CreateMessage now uses the same ${messageId}-${index} key as streamed Droid deltas, so final blocks with SDK id fields do not bypass deduplication.
  • Updated the assistant text and thinking dedup regression tests to include final content block ids.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/Layers/DroidProvider.ts
Comment thread apps/web/src/components/ChatView.tsx Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the latest Droid review batch in 450fa08c.

  • Resumed Droid sessions now immediately apply the requested autonomy/access level plus current model/reasoning settings via updateSettings.
  • Starting Droid twice for the same T3 thread now closes the replaced SDK session before installing the new context.
  • User interrupts now complete aborted Droid streams as interrupted instead of surfacing provider failures.
  • Droid SDK error stream messages now make the active turn fail and preserve the session error state.
  • Rollback now fails as unsupported for Droid instead of mutating only T3 local snapshots while the SDK session keeps stale history.
  • Final assistant completion dedup uses the same message/block key as streamed and final text content.
  • Model discovery now passes the Effect cancellation signal through to createSession so timeout interruption can close the probe process.
  • Runtime-mode normalization in ChatView now waits for provider config before persisting fallback normalization, avoiding temporary Droid medium-access downgrades.

Validation: bun fmt, bun lint (existing 9 warnings only), bun typecheck, and full bun run test all pass.

Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Resolved the latest Bugbot findings in 969aceb.

  • Made ChatView runtime-mode normalization instance-aware and gated persistence until the selected provider instance is actually present/stable, so Droid medium-access does not get downgraded during reconnect/provider reload fallbacks.
  • Added a Droid sendTurn active-turn guard to reject overlapping turns before shared mutable context is reset, plus a regression test for concurrent sends.
  • Updated the snapshot/rollback test to wait for turn completion between serialized sends.

Validation on this commit:

  • bun fmt
  • bun lint (existing warnings only, 0 errors)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 17, 2026

Resolved the latest Droid PR reports in a1c14570:

  • made detached Droid event emission tolerate adapter queue shutdown during teardown
  • reserved activeAbort before attachment resolution so concurrent turns cannot pass the guard while file I/O is pending
  • release the reservation on attachment/empty-turn validation failures, with a regression test
  • made provider driver resolution require enabled + available instances, and let ChatView normalize known-disabled selections against the fallback provider instead of preserving Droid-only medium-access

Validation:

  • bun fmt
  • bun lint (existing 9 warnings, 0 errors)
  • bun typecheck
  • bun run test

Comment thread apps/web/src/components/ChatView.tsx
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 17, 2026

Resolved the latest Macroscope comment in 30725c36.

  • runtime mode normalization now waits for an enabled/available explicit provider selection instead of falling back to Codex for known disabled/unavailable instances
  • added ChatView logic coverage for unloaded config, no explicit selection, selectable provider, disabled provider, and locked sessions

Validation:

  • bun fmt
  • bun lint (existing 9 warnings, 0 errors)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 17, 2026

Resolved the latest Cursor Bugbot comments in 23191e94.

  • stopSession now treats session.exited emission as best-effort so stopAll cleanup is not interrupted by queue shutdown/emission failures
  • Droid intra-turn token usage updates now accumulate from the active usage snapshot instead of always restarting from the turn baseline
  • added Droid adapter coverage for multiple TokenUsageUpdate messages in a single turn

Validation:

  • bun run --cwd apps/server test src/provider/Layers/DroidAdapter.test.ts
  • bun fmt
  • bun lint (existing 9 warnings, 0 errors)
  • bun typecheck
  • bun run test

cumulativeTokenUsage: undefined,
};
contextRef = context;
const previousContext = sessions.get(input.threadId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low Layers/DroidAdapter.ts:239

Concurrent startSession calls for the same threadId can orphan a DroidContext. At lines 239-243, closeContext(previousContext) yields during Effect.tryPromise, so two fibers can both pass the sessions.get() check, race to sessions.set(), and the loser's droid.close() is never called—neither here nor in the finalizer, which only closes contexts currently in the map.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/DroidAdapter.ts around line 239:

Concurrent `startSession` calls for the same `threadId` can orphan a `DroidContext`. At lines 239-243, `closeContext(previousContext)` yields during `Effect.tryPromise`, so two fibers can both pass the `sessions.get()` check, race to `sessions.set()`, and the loser's `droid.close()` is never called—neither here nor in the finalizer, which only closes contexts currently in the map.

Evidence trail:
apps/server/src/provider/Layers/DroidAdapter.ts line 72: `const sessions = new Map<ThreadId, DroidContext>()` — plain Map, no synchronization.
lines 118-122: `closeContext` is `Effect.tryPromise(() => { context.activeAbort?.abort(); return context.droid.close(); }).pipe(Effect.ignore)` — yields but does NOT remove entry from map.
lines 239-243: check-then-act pattern with yield point at line 241 (`yield* closeContext(previousContext)`).
lines 81-96: finalizer only closes `[...sessions.values()]` — orphaned contexts not in map are missed.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 23191e9. Configure here.

});
const runtimeMode = canNormalizeRuntimeMode
? normalizeRuntimeModeForProvider(selectedProvider, rawRuntimeMode)
: rawRuntimeMode;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled Droid shows mismatched runtime mode selector state

Medium Severity

When a user has explicitly selected the Droid provider but it becomes disabled or unavailable, resolvedUnlockedProvider is undefined, so selectedProvider falls back to "codex". Meanwhile, canNormalizeRuntimeMode is false (correctly preventing persistence of a downgrade), so runtimeMode stays as "medium-access". The ChatComposer then receives provider="codex" and runtimeMode="medium-access"getRuntimeModeOptions("codex") excludes "medium-access", so the Select component's current value doesn't match any dropdown option. The trigger label/icon renders fine (from the config map), but the popup has no highlighted selection and the user can't re-select the current mode.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 23191e9. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants